﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Cysharp.Threading.Tasks;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Internal.Runtime.Net;
using KuiHuaBaoDian.Services.Net;
using Runtime.Services.NetSimulate;

namespace Runtime.Net {

    public interface IDFR_NetSessionSetter {

        void SetDefaultResponseTimeout(float value);
    }

    public sealed class DFR_NetSession : IDFR_NetSessionSetter, IDFR_NetSessionInternal {

        public IDFR_NetSessionSetter AsSetter() => this;

        private readonly struct ErrorCodeName {

            public string Value { get; init; }
        }
        private static readonly IDictionary<ErrorCode, ErrorCodeName> ERROR_CODE_NAME_MAP = new Dictionary<ErrorCode, ErrorCodeName>();

        [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]
        static void InitializeOnLoad() {
            var fields = typeof(ErrorCode).GetFields(BindingFlags.Public | BindingFlags.Static);
            foreach (var field in fields) {
                var attribute = field.GetCustomAttribute<OriginalNameAttribute>();
                if (attribute != null) {
                    ERROR_CODE_NAME_MAP.Add((ErrorCode)field.GetValue(null), new ErrorCodeName { Value = attribute.Name });
                }
            }
        }

        public NetConnectionHandle ConnectionHandle { get; private set; }

        private static readonly NetPacketID<uint> NO_RESPONSE_MESSAGE_ID = new(0);

        public bool IsConnected => !DFR_Core.Net.IsEnabled || ConnectionHandle.IsConnected;

        private float m_DefaultResponseTimeout;
        void IDFR_NetSessionSetter.SetDefaultResponseTimeout(float value) => m_DefaultResponseTimeout = value;

        private INetReceiver m_Receiver;
        void IDFR_NetSessionInternal.SetReceiver(INetReceiver receiver) => m_Receiver = receiver;

        private INetSender m_Sender;
        void IDFR_NetSessionInternal.SetSender(INetSender sender) => m_Sender = sender;

        public bool IsPublic { get; private set; }

        public DFR_NetSession(NetConnectionHandle connectionHandle) {
            ConnectionHandle = connectionHandle;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private async UniTask<TResponse> Wait<TResponse>(CancellationToken cancellationToken) where TResponse : IMessage => (TResponse)await Wait(typeof(TResponse), cancellationToken);
        private async UniTask<IMessage> Wait(Type responseMessageType, CancellationToken cancellationToken) {
            UnityEngine.Assertions.Assert.IsNotNull(responseMessageType);
            UnityEngine.Assertions.Assert.IsTrue(typeof(IMessage).IsAssignableFrom(responseMessageType));

            if (KHBD.Context.IsCancellationRequested(cancellationToken)) {
                return null;
            }

            var responseMessage = default(IMessage);
            var hasResponse = false;
            var isError = false;
            var responsePacketID = DFR_NetUtility.GetPacketID(responseMessageType);
            var responseMessageID = NO_RESPONSE_MESSAGE_ID;
            var callbackID = new NetCallbackID { GUID = Guid.NewGuid() };
            if (responsePacketID != DFR_NetUtility.INVALID_PACKET_ID) {
                responseMessageID = new NetPacketID<uint>(responsePacketID.Value);
                m_Receiver.AddListener(responseMessageID, callbackID, packet => {
                    if (DFR_NetUtility.TryGetMessage(responseMessageType, packet, out responseMessage)) {
                        hasResponse = true;
                    } else {
                        isError = true;
                    }
                }, isOneTime: true);
            } else {
                throw new InvalidProgramException($"没有找到 {responseMessageType.FullName} 对应的消息ID");
            }
            try {
                while (responsePacketID != DFR_NetUtility.INVALID_PACKET_ID && !hasResponse) {
                    cancellationToken.ThrowIfCancellationRequested();
                    if (isError) {
                        throw new InvalidProgramException();
                    }
                    await UniTask.Yield();
                }
            } catch (OperationCanceledException) {
                if (responsePacketID != DFR_NetUtility.INVALID_PACKET_ID) {
                    m_Receiver.RemoveListener(responseMessageID, callbackID);
                }
                //Debug.LogError($"请求超时[{nameof(TRequest)}={typeof(TRequest).Name},{nameof(TResponse)}={typeof(TResponse).Name}]");
                //Debug.LogException(e);
                //throw e;
            } catch (InvalidProgramException) {
                throw new InvalidProgramException($"{responseMessageType.FullName} 消息获取失败, 消息ID {responsePacketID}");
            }
            return responseMessage;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UniTask<TResponse> Send<TRequest, TResponse>(TRequest requestMessage, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse : IMessage {
            return Send<TRequest, TResponse>(requestMessage, CancellationToken.None, allowAlertErrorCode);
        }
        public UniTask<TResponse> Send<TRequest, TResponse>(TRequest requestMessage, int timeoutMilliseconds, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse : IMessage {
            var cancellationToken = CancellationToken.None;
            if (timeoutMilliseconds > 0) {
                var cts = new CancellationTokenSource();
                cts.CancelAfter(timeoutMilliseconds);
                cancellationToken = cts.Token;
            }
            return Send<TRequest, TResponse>(requestMessage, cancellationToken, allowAlertErrorCode);
        }
        public async UniTask<TResponse> Send<TRequest, TResponse>(TRequest requestMessage, CancellationToken cancellationToken, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse : IMessage {
            var task = Wait<TResponse>(cancellationToken);
            await Send(requestMessage);
            var responseMessage = await task;
            Alert(allowAlertErrorCode, cancellationToken, responseMessage);
            return responseMessage;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2, TResponse3 response3, TResponse4 response4)> Send<TRequest, TResponse1, TResponse2, TResponse3, TResponse4>(TRequest requestMessage, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage where TResponse3 : IMessage where TResponse4 : IMessage {
            return Send<TRequest, TResponse1, TResponse2, TResponse3, TResponse4>(requestMessage, CancellationToken.None, allowAlertErrorCode);
        }
        public async UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2, TResponse3 response3, TResponse4 response4)> Send<TRequest, TResponse1, TResponse2, TResponse3, TResponse4>(TRequest requestMessage, CancellationToken cancellationToken, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage where TResponse3 : IMessage where TResponse4 : IMessage {
            var (responseIndex, responseMessage) = await Send(new Type[] { typeof(TResponse1), typeof(TResponse2), typeof(TResponse3), typeof(TResponse4) }, requestMessage, cancellationToken, allowAlertErrorCode);
            return responseIndex switch {
                0 => (responseIndex, (TResponse1)responseMessage, default, default, default),
                1 => (responseIndex, default, (TResponse2)responseMessage, default, default),
                2 => (responseIndex, default, default, (TResponse3)responseMessage, default),
                3 => (responseIndex, default, default, default, (TResponse4)responseMessage),
                _ => throw new NotSupportedException(),
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2, TResponse3 response3)> Send<TRequest, TResponse1, TResponse2, TResponse3>(TRequest requestMessage, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage where TResponse3 : IMessage {
            return Send<TRequest, TResponse1, TResponse2, TResponse3>(requestMessage, CancellationToken.None, allowAlertErrorCode);
        }
        public async UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2, TResponse3 response3)> Send<TRequest, TResponse1, TResponse2, TResponse3>(TRequest requestMessage, CancellationToken cancellationToken, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage where TResponse3 : IMessage {
            var (responseIndex, responseMessage) = await Send(new Type[] { typeof(TResponse1), typeof(TResponse2), typeof(TResponse3) }, requestMessage, cancellationToken, allowAlertErrorCode);
            return responseIndex switch {
                0 => (responseIndex, (TResponse1)responseMessage, default, default),
                1 => (responseIndex, default, (TResponse2)responseMessage, default),
                2 => (responseIndex, default, default, (TResponse3)responseMessage),
                _ => throw new NotSupportedException(),
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2)> Send<TRequest, TResponse1, TResponse2>(TRequest requestMessage, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage {
            return Send<TRequest, TResponse1, TResponse2>(requestMessage, CancellationToken.None, allowAlertErrorCode);
        }
        public async UniTask<(int responseIndex, TResponse1 response1, TResponse2 response2)> Send<TRequest, TResponse1, TResponse2>(TRequest requestMessage, CancellationToken cancellationToken, bool allowAlertErrorCode = true) where TRequest : IMessage where TResponse1 : IMessage where TResponse2 : IMessage {
            var (responseIndex, responseMessage) = await Send(new Type[] { typeof(TResponse1), typeof(TResponse2) }, requestMessage, cancellationToken, allowAlertErrorCode);
            if (responseIndex == 0) {
                return (responseIndex, (TResponse1)responseMessage, default);
            } else {
                return (responseIndex, default, (TResponse2)responseMessage);
            }
        }

        private async UniTask<(int responseIndex, IMessage responseMessage)> Send<TRequest>(Type[] responseTypes, TRequest requestMessage, CancellationToken cancellationToken, bool allowAlertErrorCode = true) where TRequest : IMessage {
            UnityEngine.Assertions.Assert.IsNotNull(responseTypes);
            UnityEngine.Assertions.Assert.IsTrue(responseTypes.Length > 0);

            var tasks = new UniTask<IMessage>[responseTypes.Length];
            var ctss = new CancellationTokenSource[responseTypes.Length];
            for (var i = 0; i < responseTypes.Length; ++i) {
                var responseType = responseTypes[i];
                var cts = (m_DefaultResponseTimeout > 0) ? new CancellationTokenSource((int)(m_DefaultResponseTimeout * 1000)) : new CancellationTokenSource();
                cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, cts.Token);
                cancellationToken = cts.Token;
                ctss[i] = cts;
                var task = Wait(responseType, cancellationToken);
                tasks[i] = task;
            }
            await Send(requestMessage);

            var (responseIndex, responseMessage) = await UniTask.WhenAny(tasks);
            for (var i = 0; i < responseTypes.Length; ++i) {
                if (i != responseIndex) {
                    var cts = ctss[i];
                    cts.Cancel();
                }
            }
            Alert(allowAlertErrorCode, cancellationToken, responseMessage);

            return (responseIndex, responseMessage);
        }

        private void Alert(bool allowAlertErrorCode, CancellationToken cancellationToken, params IMessage[] responseMessages) {
            if (allowAlertErrorCode && !KHBD.Context.IsCancellationRequested(cancellationToken)) {
                foreach (var responseMessage in responseMessages) {
                    if (DFR_NetUtility.TryGetErrorCode(responseMessage, out var errorCode)) {
                        if (errorCode != ErrorCode.SUCCESS) {
                            //if (DFR.DataCenter.Tables.TBGameCodetips.TryGet(ERROR_CODE_NAME_MAP[errorCode].Value, out var codetips)) {
                            //    EmitService.Message(codetips.Desc);
                            //} else {
#if UNITY_EDITOR
                            UnityEngine.Debug.LogError($"{nameof(DFR_NetSession)} {nameof(errorCode)} = {errorCode}");
#else
                            UnityEngine.Debug.LogWarning($"{nameof(DFR_NetSession)} {nameof(errorCode)} = {errorCode}");
#endif
                            //}
                        }
                    }
                }
            }
        }

        private sealed record RequestPacket(INetPacketID ID, byte[] Body) : INetPacket;
        public async UniTask Send<TRequest>(TRequest requestMessage) where TRequest : IMessage {
            if (DFR_Core.Net.IsEnabled) {
                var requestPacketID = DFR_NetUtility.GetPacketID<TRequest>();
                var requestMessageID = new NetPacketID<uint>(requestPacketID.Value);
                var requestPackage = new RequestPacket(requestMessageID, requestMessage.ToByteArray());
                await m_Sender.Send(ConnectionHandle, requestPackage);
            } else {
                await (m_Sender as IDFR_NetSimulateSender).Send(requestMessage);
            }
        }

        public void Dispose() {
            ConnectionHandle = NetConnectionHandle.None;
            m_Receiver = null;
            m_Sender = null;
        }

        void IDFR_NetSessionInternal.SetPublic() => IsPublic = true;
    }
}

namespace Internal.Runtime.Net {

    public interface IDFR_NetSessionInternal {

        void SetReceiver(INetReceiver receiver);

        void SetSender(INetSender sender);

        void SetPublic();
    }
}
